package com.h117121.fashionshop.core.dao.generic;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.hibernate.Query;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.h117121.fashionshop.core.dom.DateQueryType;
import com.h117121.fashionshop.core.dom.ResultPage;
import com.h117121.fashionshop.core.exception.EntityNotFoundException;

@Repository
public class GenericDaoImpl<T, Id extends Serializable> implements
		GenericDao<T, Long> {
	@Autowired
	protected SessionFactory sessionFactory;

	private static String queryDate = "from {0} where {1} {2} ''{3}''";

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public Class getEntityClass() {
		return (Class<T>) ((ParameterizedType) getClass()
				.getGenericSuperclass()).getActualTypeArguments()[0];
	}

	@Override
	public void saveOrUpdate(T entity) {
		sessionFactory.getCurrentSession().saveOrUpdate(entity);
	}

	@Override
	public void delete(T entity) {
		sessionFactory.getCurrentSession().delete(entity);
	}

	@SuppressWarnings("unchecked")
	@Override
	public T findById(long id) throws EntityNotFoundException {
		T entity = (T) sessionFactory.getCurrentSession().get(getEntityClass(),
				id);
		if (entity == null)
			throw new EntityNotFoundException();
		return entity;
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<T> getAll() {
		return sessionFactory.getCurrentSession()
				.createQuery("from " + getEntityClass().getSimpleName()).list();
	}

	@SuppressWarnings("unchecked")
	protected List<T> findByDate(String field, DateQueryType type, Date date) {
		List<T> rs = new ArrayList<T>();
		if (type == DateQueryType.AFTER) {
			rs = sessionFactory
					.getCurrentSession()
					.createQuery(
							MessageFormat.format(queryDate, getEntityClass()
									.getSimpleName(), field, ">", date)).list();
		} else if (type == DateQueryType.BEFORE) {
			rs = sessionFactory
					.getCurrentSession()
					.createQuery(
							MessageFormat.format(queryDate, getEntityClass()
									.getSimpleName(), field, "<", date)).list();
		} else {
			rs = sessionFactory
					.getCurrentSession()
					.createQuery(
							MessageFormat
									.format("from {0} where {1} >= ''{2}'' and {1} < ''{3}''",
											getEntityClass().getSimpleName(),
											field, DateUtils.truncate(date,
													Calendar.DATE),
											DateUtils.addDays(DateUtils
													.truncate(date,
															Calendar.DATE), 1)))
					.list();
		}
		return rs;

	}

	/**
	 * Fix single-quote(') problem by add one more single-quote(') after every
	 * single-quote('). <br>
	 * Example: O'no -> O''no
	 */
	protected String fixSingleQuote(String content) {
		return StringUtils.replace(content, "'", "''");
	}

	/**
	 * Fix single-quote(') problem, add % and make the content lowercase.<br>
	 * Example: O'no -> '%o''no%'
	 */
	protected String formatLikeString(String content) {
		StringBuilder fixed = new StringBuilder(
				fixSingleQuote(StringUtils.lowerCase(content)));
		fixed.insert(0, "'%");
		fixed.append("%'");
		return fixed.toString();
	}

	/**
	 * Add single-quote(') for comparing string in query statement. <br>
	 * Example: xxx -> 'xxx'
	 */
	protected String addSingleQuote(String content) {
		StringBuilder fixed = new StringBuilder(content);
		fixed.insert(0, "'");
		fixed.append("'");
		return fixed.toString();
	}

	public SessionFactory getSessionFactory() {
		return sessionFactory;
	}

	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}

	@Override
	public ResultPage getAll(int index, int numItems) {
		Query query = getSessionFactory().getCurrentSession().createQuery(
				"from " + getEntityClass().getSimpleName() + " order by id");
		query.setMaxResults(numItems);
		query.setFirstResult(index * numItems);

		long totalNumItems = (((Long) getSessionFactory()
				.getCurrentSession()
				.createQuery(
						"select count(*) from "
								+ getEntityClass().getSimpleName()).iterate()
				.next()).longValue());
		long numPage = (totalNumItems % numItems == 0) ? (totalNumItems / numItems)
				: (1 + totalNumItems / numItems);

		ResultPage result = new ResultPage();
		result.setNumPage(numPage);
		result.setCurrentPage(index);
		result.setNumItems(totalNumItems);
		result.setResult(query.list());

		return result;
	}

	@Override
	public ResultPage getByKeyword(String keyword, int index, int numItems) {
		if (keyword == null || keyword.isEmpty()) {
			return getAll(index, numItems);
		}

		long id = -1;
		try {
			id = Long.parseLong(keyword);
		} catch (NumberFormatException ex) {
		}

		Query query = getSessionFactory().getCurrentSession().createQuery(
				"from " + getEntityClass().getSimpleName()
						+ " where lower(name) like "
						+ formatLikeString(keyword)
						+ " OR lower(description) like "
						+ formatLikeString(keyword)
						+ ((id != -1) ? (" OR id = " + id) : ""));
		query.setMaxResults(numItems);
		query.setFirstResult(index * numItems);

		long totalNumItems = (((Long) getSessionFactory()
				.getCurrentSession()
				.createQuery(
						"select count(*) from "
								+ getEntityClass().getSimpleName()
								+ " where lower(name) like "
								+ formatLikeString(keyword)
								+ " OR lower(description) like "
								+ formatLikeString(keyword)
								+ ((id != -1) ? (" OR id = " + id) : ""))
				.iterate().next()).longValue());
		long numPage = (totalNumItems % numItems == 0) ? (totalNumItems / numItems)
				: (1 + totalNumItems / numItems);

		ResultPage result = new ResultPage();
		result.setNumPage(numPage);
		result.setCurrentPage(index);
		result.setNumItems(totalNumItems);
		result.setResult(query.list());

		return result;
	}
}
